/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.metadata.rtcustomization.servermanager.deploymanager;

import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.MetadataDto;
import com.inspur.edp.lcm.metadata.common.MetadataDtoConverter;
import com.inspur.edp.metadata.rtcustomization.api.AbstractCustomizedContent;
import com.inspur.edp.metadata.rtcustomization.api.entity.DimensionExtendEntity;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdChangeset;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdExtRelation;
import com.inspur.edp.metadata.rtcustomization.serverapi.CustomizationServerService;
import com.inspur.edp.metadata.rtcustomization.servermanager.entity.MetadataSyncInfo;
import com.inspur.edp.metadata.rtcustomization.servermanager.event.MetadataDeployEventBroker;
import com.inspur.edp.metadata.rtcustomization.servermanager.extend.DeployStrategy;
import com.inspur.edp.metadata.rtcustomization.servermanager.utils.MetadataDeployUtils;
import com.inspur.edp.metadata.rtcustomization.spi.CustomizationExtHandler;
import com.inspur.edp.metadata.rtcustomization.spi.CustomizationSerializer;
import com.inspur.edp.metadata.rtcustomization.spi.event.ExtMdSavedArgs;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

/**
 * @author zhaoleitr
 */
@Slf4j
public class MdRtCustomizationDeployManager {

	private static MdRtCustomizationDeployManager instance;
	private Map<String, AbstractCustomizedContent> changeMap = new HashMap<>();
	private final Map<String, GspMetadata> metadataMap = new HashMap<>();
	private final CustomizationServerService customizationServerService;

	private MdRtCustomizationDeployManager() {
		customizationServerService = SpringBeanUtils.getBean(CustomizationServerService.class);
	}

	public static MdRtCustomizationDeployManager getInstance() {
		if (instance == null) {
			instance = new MdRtCustomizationDeployManager();
		}
		return instance;
	}

	public void deployCustomizationMdList(List<GspMetadata> metadataList, List<GspMdExtRelation> extRelations, List<GspMdChangeset> changeSets) {
		List<MetadataSyncInfo> infoList = new ArrayList<>();
		for (GspMetadata metadata : metadataList) {
			List<DimensionExtendEntity> extendEntitys = customizationServerService.getMetadataInfoListRecusively(metadata.getHeader().getId(), metadata.getHeader().getCertId());
			MetadataDto rootMetadataDto = customizationServerService.getBasicMetadataByExtMdId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
			GspMetadata rootMetadata= MetadataDtoConverter.asMetadata(rootMetadataDto);
			//构造向下同步实体，同时检查数据完整性
			MetadataSyncInfo info = getSyncInfo(metadata, extRelations, changeSets, extendEntitys, rootMetadata);
			infoList.add(info);
			for (DimensionExtendEntity extendEntity : extendEntitys) {
				if (extendEntity.getBasicMetadataId().equals(metadata.getHeader().getId())) {
					GspMetadata extendMetadata = customizationServerService.getMetadata(extendEntity.getExtendMetadataEntity().getHeader().getId());
					GspMetadata oldMetadata = customizationServerService.getMetadata(metadata.getHeader().getId());
					AbstractCustomizedContent parentChange = DeployStrategy.getExtContent4SameLevel(metadata, oldMetadata, rootMetadata);
					//运行时定制元数据冲突检查
					checkConflict(metadata, extendMetadata, parentChange, extendEntitys, rootMetadata);
				}
			}
		}
		//同步保存
		syncMetadataInfos(infoList);
		clearSyncInfo();
	}

	private MetadataSyncInfo getSyncInfo(GspMetadata metadata, List<GspMdExtRelation> extRelations, List<GspMdChangeset> changeSets, List<DimensionExtendEntity> extendEntities, GspMetadata rootMetadata) {
		MetadataSyncInfo info = new MetadataSyncInfo(metadata);
		GspMdExtRelation extRelation = extRelations.stream().filter(item -> item.getExtMdId().equals(metadata.getHeader().getId())).findAny().orElse(null);
		if (extRelation == null) {
			extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
			if (extRelation == null) {
				throw new RuntimeException("运行时定制元数据扩展关系不存在");
			}
		}
		info.setExtRelation(extRelation);

		GspMdChangeset changeSet = changeSets.stream().filter(item -> item.getMetadataId().equals(metadata.getHeader().getId())).findAny().orElse(null);
		if (changeSet != null) {
			info.setChangeSet(changeSet);
		}

		//如果目标环境中有元数据，检查元数据是否被扩展，如果被扩展了，检查冲突
		if (CollectionUtils.isEmpty(extendEntities)) {
			return info;
		}

		info.setNeedSync(true);
		info.setDimensionExtendEntityList(extendEntities);
		info.setRootMetadata(rootMetadata);
		return info;
	}


	private void syncMetadataInfos(List<MetadataSyncInfo> infoList) {
		for (MetadataSyncInfo info : infoList) {
			GspMetadata metadata = info.getMetadata();
			GspMetadata preversionMetadata = customizationServerService.getMetadata(metadata.getHeader().getId());
			if (preversionMetadata != null) {
				metadata.setVersion(String.valueOf(Integer.parseInt(preversionMetadata.getVersion()) + 1));
				metadata.setPreviousVersion(preversionMetadata.getVersion());
			} else {
				//没有前一个版本，则版本为1
				metadata.setVersion("1");
			}

			//保存扩展关系
			saveExtRelation(info, metadata);

			//保存当前元数据
			customizationServerService.saveExtMetadata(metadata);

			//运行时定制元数据保存后事件

			fireExtMdSavedEvent(metadata, info.getExtRelation(), null);

			//启用到运行时
			customizationServerService.releaseMetadataToRt(metadata.getHeader().getId(),metadata.getHeader().getCertId());

			//保存变更集
			saveChangeSet(info, metadata, preversionMetadata);

			//同步
			if (info.isNeedSync()) {
				syncMetadata(info.getMetadata(), info.getDimensionExtendEntityList(), MetadataDeployUtils.getCustomizationExtHandler(info.getMetadata().getHeader().getType()), info.getRootMetadata());
			}
		}
	}

	public void checkConflict(GspMetadata basicMetadata, GspMetadata metadata, AbstractCustomizedContent parentChange, List<DimensionExtendEntity> extendEntitys, GspMetadata rootMetadata) {
		//当元数据修改增量为空时，不需要进行合并处理
		if (Objects.isNull(parentChange)){
             return;
		}
		AbstractCustomizedContent changeToParent = null;
		GspMdChangeset mdChangeset = customizationServerService.getChangeset(metadata.getHeader().getId(), metadata.getHeader().getCertId());
		CustomizationSerializer changeSetSerializerManager = MetadataDeployUtils.getChangeSetSerializer(metadata.getHeader().getType());
		if (mdChangeset != null) {
			changeToParent = (AbstractCustomizedContent) changeSetSerializerManager.deSerialize(mdChangeset.getContent());
			DeployStrategy.checkMergeConflict(basicMetadata.getHeader().getType(), parentChange, changeToParent, rootMetadata);
		}

		AbstractCustomizedContent newParentChange;
		if (changeToParent == null) {
			newParentChange = parentChange;
		} else {
			newParentChange = DeployStrategy.changeMerge(metadata.getHeader().getType(), parentChange, changeToParent, rootMetadata,basicMetadata,metadata);
		}
		//将合并的增量记录下来，合并时使用
		if (newParentChange == null) {
			return;
		}
		AbstractCustomizedContent clonedNewParentChange = CloneNewParentChange(newParentChange, changeSetSerializerManager);
		//because of extendmetadata has its own changeset.
		// if basicmetadata has many extendmetadatas, the changes must be seperated by extendmetadata id ,not with the same basicmetadata id
		changeMap.put(metadata.getHeader().getId(), clonedNewParentChange);
        metadataMap.put(metadata.getHeader().getId(), metadata);
		//find the new extendmetadata, the metatadata id become parent id
		//find all extendmetadatas, if exist, checkConflict. if not exist,finish.
		for (DimensionExtendEntity entity : extendEntitys){
			if (entity.getBasicMetadataId().equals(metadata.getHeader().getId())){
                 GspMetadata extendMetadata = customizationServerService.getMetadata(entity.getExtendMetadataEntity().getHeader().getId());
                 //exit condition is extend relation not exits
				//从扩展链中找到当前元数据的扩展元数据，递归向下检查冲突
                 checkConflict(metadata, extendMetadata, newParentChange, extendEntitys, rootMetadata);
			}

		}

	}

	private AbstractCustomizedContent CloneNewParentChange(AbstractCustomizedContent change, CustomizationSerializer changeSetSerializerManager) {
		String contentStr = changeSetSerializerManager.serialize(change);
		return (AbstractCustomizedContent)changeSetSerializerManager.deSerialize(contentStr);
	}

	public void syncMetadata(GspMetadata metadata, List<DimensionExtendEntity> dimensionExtendEntityList, CustomizationExtHandler manager, GspMetadata rootMetadata) {
		if (Objects.nonNull(manager)) {
			for (DimensionExtendEntity entity : dimensionExtendEntityList) {
				if (!entity.getBasicMetadataId().equals(metadata.getHeader().getId())) {
					continue;
				}
				//find extend metadata and mergedChange
				AbstractCustomizedContent mergedChange = changeMap.get(entity.getExtendMetadataEntity().getHeader().getId());
				//如果获取增量为空，说明不需要合并，继续查找需要合并的元数据。
				if (Objects.isNull(mergedChange)) {
					continue;
				}
				GspMetadata extMetadata = metadataMap.get(entity.getExtendMetadataEntity().getHeader().getId());
				if (Objects.isNull(extMetadata)) {
					log.info(String.format("存在扩展关系，但扩展元数据内容为空。扩展关系为：%s [type:%s id:%s]扩展了%s [id:%s]", entity.getExtendMetadataEntity().getHeader().getCode(), entity.getExtendMetadataEntity().getHeader().getType(), entity.getExtendMetadataEntity().getHeader().getId(), entity.getBasicMetadataCode(), entity.getBasicMetadataId()));
					continue;
				}
				GspMetadata mergedMetadata = DeployStrategy.mergeMetadata(manager, rootMetadata, metadataMap.get(entity.getExtendMetadataEntity().getHeader().getId()), mergedChange);

				int version = Integer.parseInt(mergedMetadata.getVersion());
				String newVersion = String.valueOf(version + 1);
				mergedMetadata.setPreviousVersion(String.valueOf(version));
				mergedMetadata.setVersion(newVersion);

				GspMdExtRelation extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
				if (extRelation == null) {
					throw new RuntimeException("运行时定制元数据扩展关系不存在");
				}
				extRelation.setBasicMdVersion(String.valueOf(version));
				extRelation.setExtMdVersion(newVersion);

				//保存扩展关系
				customizationServerService.saveMetadataExtRelation(extRelation);

				//保存扩展元数据
				customizationServerService.saveExtMetadata(mergedMetadata);
				customizationServerService.releaseMetadataToRt(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
				//元数据扩展后事件
				fireExtMdSavedEvent(mergedMetadata, extRelation, mergedChange);

				GspMdChangeset changeSet = customizationServerService.getChangeset(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
				if (changeSet != null) {
					extRelation.setBasicMdVersion(String.valueOf(version));
					changeSet.setVersion(newVersion);
					customizationServerService.saveMdChangeSet(changeSet);
				}

				syncMetadata(mergedMetadata, dimensionExtendEntityList, manager, rootMetadata);
			}
		}
	}

	private void saveChangeSet(MetadataSyncInfo info, GspMetadata metadata, GspMetadata preversionMetadata) {
		if (info.getChangeSet() == null) {
			GspMdChangeset changeSet = customizationServerService.getChangeset(metadata.getHeader().getId(), metadata.getHeader().getCertId());
			if (changeSet != null) {
				info.setChangeSet(changeSet);
			}
		}
		if (info.getChangeSet() != null) {
			info.getChangeSet().setVersion(metadata.getVersion());
			if (preversionMetadata != null) {
				info.getChangeSet().setPreviousVersion(preversionMetadata.getVersion());
			}
			customizationServerService.saveMdChangeSet(info.getChangeSet());
		}
	}

	private void fireExtMdSavedEvent(GspMetadata metadata, GspMdExtRelation extRelation, AbstractCustomizedContent mergedChange) {
		MetadataDeployEventBroker deployEventListener = SpringBeanUtils.getBean(MetadataDeployEventBroker.class);
		ExtMdSavedArgs args = new ExtMdSavedArgs();
		DimensionExtendEntity dimensionExtendEntity = buildDimensionEntity(metadata, extRelation);
		args.setDimensionExtendEntity(dimensionExtendEntity);
		args.setMergedChange(mergedChange);
		deployEventListener.fireExtMdSavedEvent(args);
	}

	private void saveExtRelation(MetadataSyncInfo info, GspMetadata metadata) {
		if (info.getExtRelation() == null) {
			GspMdExtRelation extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
			if (extRelation == null) {
				throw new RuntimeException("运行时定制元数据扩展关系不存在");
			}
			info.setExtRelation(extRelation);
		}

		info.getExtRelation().setExtMdVersion(metadata.getVersion());
		customizationServerService.saveMetadataExtRelation(info.getExtRelation());
	}

	private DimensionExtendEntity buildDimensionEntity(GspMetadata metadata, GspMdExtRelation extRelation) {
		DimensionExtendEntity dimensionExtendEntity = new DimensionExtendEntity();
		dimensionExtendEntity.setFirstDimensionCode(extRelation.getFirstDimCode());
		dimensionExtendEntity.setFirstDimension(extRelation.getFirstDimValue());
		dimensionExtendEntity.setFirstDimensionName(extRelation.getFirstDimName());
		dimensionExtendEntity.setSecondDimensionCode(extRelation.getSecDimCode());
		dimensionExtendEntity.setSecondDimension(extRelation.getSecDimValue());
		dimensionExtendEntity.setSecondDimensionName(extRelation.getSecDimName());
		dimensionExtendEntity.setExtendMetadataEntity(metadata);
		dimensionExtendEntity.setBasicMetadataVersion(extRelation.getBasicMdVersion());
		dimensionExtendEntity.setBasicMetadataTypeStr(extRelation.getBasicMdType());
		dimensionExtendEntity.setBasicMetadataNamespace(extRelation.getBasicMdNameSpace());
		dimensionExtendEntity.setBasicMetadataId(extRelation.getBasicMdId());
		dimensionExtendEntity.setBasicMetadataCode(extRelation.getBasicMdCode());
		dimensionExtendEntity.setBasicMetadataCertId(extRelation.getExtMdCertId());

		return dimensionExtendEntity;
	}

	public void clearSyncInfo() {
		this.changeMap = new HashMap<>();
	}
}
